/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Simulador.Hardware;

import Auxiliar.FilaEventos;
import Simulador.Event.Evento;
import Simulador.Event.InfoEventoArquivo;
import Simulador.Job.Segmento;

/**
 *
 * @author Fernando
 * @author Matheus
 */
public class Arquivo {

    private int discoId;
    private int arquivoId;
    private FilaEventos fEspera;
    private Segmento escrevendo;
    private semaforoArquivo arq;
    private float TEMPO_REQUERIR_IO = .1f;

    Arquivo(int DiscoId, int arquivoId) {
        this.discoId = DiscoId;
        this.arquivoId = arquivoId;
        arq = new semaforoArquivo();
        fEspera = new FilaEventos();
    }

    /**
     * Libera o recurso e caso existam pedidos de IO pendentes, requere IO do Disco.
     * Retorna todas as leituras a serem executadas antes da primeira escrita encontrada.
     * Caso o primeiro item da lista seja uma escrita, retorna a escrita.
     * @param E
     * Evento a ser tratado.
     * @return
     * Fila com todos os proximos eventos.
     */
    public FilaEventos liberarRecurso(Evento E) {
        if (E.getSegmento().getId() == escrevendo.getId()) {
            escrevendo = null;
            arq.fimEscrita();
            //Cria Lista de Eventos.
            if (!fEspera.isEmpty()) {
                Evento e = (Evento) fEspera.getItem();
                FilaEventos f = new FilaEventos();
                int i = 0;
                while (e.getTipoEvento() == Evento.RequereLeituraArquivo) {
                    i++;
                    e.setMomentoDeChegada(E.getMomentoDeChegada() + TEMPO_REQUERIR_IO * i);
                    f.addItem(e);
                    arq.ler();
                    e = (Evento) fEspera.getItem();
                    if (e == null) {//Mudar. Nao e um bom jeito de resolver.
                        break;
                    }
                }

                if (e != null) {
                    if (e.getTipoEvento() == Evento.RequereEscritaArquivo) {
                        requerirRecurso(E);
                    }
                }
                return f;
            }else{
                return null;
            }
        } else {
            arq.fimLeitura();
            //No fim das leituras, adicionar o job de escrita - se existir.
            if (arq.estaLendo() == 0 && arq.estaEscrevendo()) {
                FilaEventos f = new FilaEventos();
                f.addItem(requerirRecurso(new Evento(Evento.RequereEscritaArquivo, escrevendo,
                                   E.getMomentoDeChegada(), new InfoEventoArquivo(discoId,
                                   arquivoId, true))));
                return f;
            } else {
                return null;
            }
        }
    }

    /**
     * Requere permissao de IO de arquivo
     * @param E
     * Evento a ser tratado.
     * @return
     * Evento se permissao foi concedida
     * Null se foi colocado na fila de espera.
     */
    public Evento requerirRecurso(Evento E) {
        InfoEventoArquivo ia = (InfoEventoArquivo)E.getInfo();
        boolean write = ia.eEscrita();
        if (!arq.estaEscrevendo()) { // Ninguem esta escrevendo no arquivo.

            if (write == true) {
                arq.escrever();
                escrevendo = E.getSegmento(); // Job atual escrevendo.

                if (arq.estaLendo() == 0) { // Se ninguem estiver lendo, retorna requisicao de disco.

                    Evento e = new Evento(Evento.RequereIODisco, escrevendo, E.getMomentoDeChegada(),new InfoEventoArquivo (this.discoId, this.arquivoId, write));
                    return e;
                } else { // Se alguem estiver lendo, espera liberar leitura.

                    return null;
                }
            } else {
                arq.ler();
                Evento e = new Evento(Evento.RequereLeituraArquivo, E.getSegmento(), E.getMomentoDeChegada(), new InfoEventoArquivo (this.discoId, this.arquivoId, write));
                return e;
            }
        } else { // Alguem esta escrevendo no arquivo.

            if (write == false) { // Adiciona evento de leitura na espera.

                Evento e = new Evento(Evento.RequereLeituraArquivo, E.getSegmento(), E.getMomentoDeChegada(), new InfoEventoArquivo (this.discoId, this.arquivoId, write));
                fEspera.addItem(e);
            } else { // Adiciona evento de escrita na espera.

                Evento e = new Evento(Evento.RequereEscritaArquivo, E.getSegmento(), E.getMomentoDeChegada(), new InfoEventoArquivo (this.discoId, this.arquivoId, write));
                fEspera.addItem(e);
            }
            return null;
        }
    }
}

class semaforoArquivo {

    private int lendo;
    private int escrevendo;

    public semaforoArquivo() {
        lendo = 0;
        escrevendo = 0;
    }

    public boolean estaEscrevendo() {
        return (escrevendo == 1);
    }

    public int estaLendo() {
        return lendo;
    }

    public void escrever() {
        this.escrevendo++;
        if (escrevendo > 1) {
            throw new Error("Erro no semaforo de arquivos - permissao para escrever");
        }
    }

    public void fimEscrita() {
        this.escrevendo--;
        if (escrevendo < 0) {
            throw new Error("Erro no semaforo de arquivos - fim de escrita");
        }
    }

    public void ler() {
        this.lendo++;
    }

    public void fimLeitura() {
        this.lendo--;
        if(lendo < 0){
            throw new Error("Erro no semaforo de arquivos - fim de leitura");
        }
    }
}